import os
import logging
import shutil
import time
from flask import Flask, request, render_template, send_file, redirect, url_for, session, jsonify
from werkzeug.security import generate_password_hash, check_password_hash
import socket

# 全局动态功能 - 性能监控装饰器
def performance_monitor(func):
    """
    监控函数执行时间的装饰器
    """
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        logging.info(f'{func.__name__} 执行耗时: {end_time - start_time:.4f} 秒')
        return result
    return wrapper

# 全局动态功能 - 请求计数器
request_count = 0

# 全局动态功能 - 异常处理装饰器
def error_handler(func):
    """
    统一异常处理装饰器
    """
    def wrapper(*args, **kwargs):
        global request_count
        request_count += 1
        try:
            return func(*args, **kwargs)
        except Exception as e:
            logging.error(f'请求 {request_count} 发生错误: {str(e)}')
            return jsonify({'error': '服务器内部错误'}), 500
    return wrapper

# 全局动态功能 - 日志记录增强
def log_enhancer(message):
    """
    日志记录增强装饰器
    """
    def decorator(func):
        def wrapper(*args, **kwargs):
            logging.info(f'{message} 开始执行')
            result = func(*args, **kwargs)
            logging.info(f'{message} 执行完成')
            return result
        return wrapper
    return decorator

# 全局动态功能 - 权限检查装饰器
def permission_check(required_role='user'):
    """
    权限检查装饰器
    """
    def decorator(func):
        def wrapper(*args, **kwargs):
            if 'username' not in session:
                return redirect(url_for('login'))
            if required_role == 'admin' and session['username'] != 'admin':
                logging.warning(f'{session.get("username")} 尝试访问需要管理员权限的资源')
                return jsonify({'error': '权限不足'}), 403
            return func(*args, **kwargs)
        return wrapper
    return decorator

# 全局动态功能 - 访问频率限制
class RateLimiter:
    """
    访问频率限制类
    """
    def __init__(self, max_requests, time_frame):
        self.max_requests = max_requests
        self.time_frame = time_frame
        self.requests = []

    def is_allowed(self):
        now = time.time()
        self.requests = [t for t in self.requests if now - t < self.time_frame]
        if len(self.requests) >= self.max_requests:
            return False
        self.requests.append(now)
        return True

# 全局动态功能 - 初始化访问频率限制器
rate_limiter = RateLimiter(max_requests=100, time_frame=60)

# 全局动态功能 - 响应时间统计
response_times = []

# 全局动态功能 - 安全检查装饰器
def security_check(func):
    """
    安全检查装饰器，防止 SQL 注入等攻击
    """
    def wrapper(*args, **kwargs):
        for key, value in request.args.items(): 
            if isinstance(value, str) and (';' in value or '--' in value):
                logging.warning(f'检测到可能的 SQL 注入攻击，参数: {key}={value}')
                return jsonify({'error': '非法请求'}), 400
        return func(*args, **kwargs)
    return wrapper

# 全局动态功能 - 审计日志记录装饰器
def audit_log(func):
    """
    审计日志记录装饰器
    """
    def wrapper(*args, **kwargs):
        user = session.get('username', '未登录用户')
        logging.info(f'{user} 访问了 {func.__name__}')
        return func(*args, **kwargs)
    return wrapper

# 全局动态功能 - 缓存装饰器
cache = {}

def cache_response(func):
    """
    缓存响应装饰器
    """
    def wrapper(*args, **kwargs):
        key = (func.__name__, args, frozenset(kwargs.items()))
        if key in cache:
            return cache[key]
        result = func(*args, **kwargs)
        cache[key] = result
        return result
    return wrapper

# 配置日志记录
logging.basicConfig(filename='app.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

app = Flask(__name__)
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024 * 1024  # 允许上传最大 5GB 文件

# 全局动态功能 - 初始化日志轮转
try:
    from logging.handlers import RotatingFileHandler
    handler = RotatingFileHandler('app.log', maxBytes=1024*1024, backupCount=5)
    app.logger.addHandler(handler)
except ImportError:
    logging.warning('无法导入 RotatingFileHandler，日志轮转功能不可用')

# 导入 figlet 库以生成大号字符
from pyfiglet import Figlet

app.secret_key = os.urandom(24)

# 模拟用户数据库
users = {}

# 网盘文件根目录
FILE_DIR = 'File'

# 初始化文件目录
if not os.path.exists(FILE_DIR):
    os.makedirs(FILE_DIR)

# 管理员添加用户
def add_user(username, password):
    if username in users:
        logging.warning(f'尝试添加已存在的用户: {username}')
        return False
    users[username] = generate_password_hash(password)
    logging.info(f'成功添加用户: {username}')
    return True

# 管理员删除用户
def delete_user(username):
    if username in users:
        del users[username]
        logging.info(f'成功删除用户: {username}')
        return True
    logging.warning(f'尝试删除不存在的用户: {username}')
    return False

# 管理员修改用户密码
def change_password(username, new_password):
    if username in users:
        users[username] = generate_password_hash(new_password)
        logging.info(f'成功修改用户 {username} 的密码')
        return True
    logging.warning(f'尝试修改不存在用户 {username} 的密码')
    return False

# 管理员添加文件
def add_file(family_name, file):
    family_dir = os.path.join(FILE_DIR, family_name)
    if not os.path.exists(family_dir):
        os.makedirs(family_dir)
    file_path = os.path.join(family_dir, file.filename)
    file.save(file_path)
    logging.info(f'成功为 {family_name} 添加文件: {file.filename}')

# 管理员删除文件

# 新建文件夹
def create_folder(family_name, folder_name):
    folder_path = os.path.join(FILE_DIR, family_name, folder_name)
    try:
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)
            logging.info(f'成功为 {family_name} 创建文件夹: {folder_name}')
            return True
        else:
            logging.warning(f'尝试创建已存在的文件夹: {folder_path}')
            return False
    except Exception as e:
        logging.error(f'创建文件夹 {folder_path} 时出错: {e}')
        return False



# 复制文件或文件夹
def copy_file_or_folder(src, dst):
    try:
        import shutil
        if os.path.isdir(src):
            shutil.copytree(src, dst)
        else:
            shutil.copy2(src, dst)
        logging.info(f'成功从 {src} 复制到 {dst}')
        return True
    except Exception as e:
        logging.error(f'复制 {src} 到 {dst} 时出错: {e}')
        return False

# 移动文件或文件夹
def move_file_or_folder(src, dst):
    try:
        import shutil
        shutil.move(src, dst)
        logging.info(f'成功从 {src} 移动到 {dst}')
        return True
    except Exception as e:
        logging.error(f'移动 {src} 到 {dst} 时出错: {e}')
        return False

def delete_file(family_name, file_name):
    file_path = os.path.join(FILE_DIR, family_name, file_name)
    if os.path.exists(file_path):
        os.remove(file_path)
        logging.info(f'成功为 {family_name} 删除文件: {file_name}')
        return True
    logging.warning(f'尝试删除不存在的文件: {file_path}')
    return False

# 从 FA.txt 文件加载用户信息
def load_users_from_files():
    """
    从每个家人文件夹下的 FA.txt 文件加载用户信息。

    :return: 包含用户信息的字典
    """
    user_dict = {}
    # 添加 admin 账号
    user_dict['admin'] = generate_password_hash('admin123')
    if os.path.exists(FILE_DIR):
        for family_folder in os.listdir(FILE_DIR):
            family_path = os.path.join(FILE_DIR, family_folder)
            if os.path.isdir(family_path):
                account_file = os.path.join(family_path, 'FA.txt')
                if os.path.exists(account_file):
                    try:
                        with open(account_file, 'r', encoding='utf-8') as f:
                            lines = f.readlines()
                            if len(lines) >= 2:
                                username = lines[0].strip()
                                password = lines[1].strip()
                                user_dict[username] = generate_password_hash(password)
                    except Exception as e:
                        logging.error(f'读取 {account_file} 时出错: {e}')
    return user_dict

# 登录路由
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        # 每次登录时加载用户信息
        users = load_users_from_files()
        if username in users and check_password_hash(users[username], password):
            session['username'] = username
            logging.info(f'{username} 登录成功')
            return redirect(url_for('index'))
        else:
            logging.warning(f'{username} 尝试使用错误密码登录')
            return '用户名或密码错误'
    return render_template('login.html')

# 登出路由
@app.route('/logout')
def logout():
    session.pop('username', None)
    return redirect(url_for('login'))

# 主页路由
@app.route('/')
def index():
    if 'username' not in session:
        return redirect(url_for('login'))
    files = {}
    total_space = 0
    used_space = 0
    if session['username'] == 'admin':
        if os.path.exists(FILE_DIR):
            for family_folder in os.listdir(FILE_DIR):
                family_path = os.path.join(FILE_DIR, family_folder)
                if os.path.isdir(family_path):
                    files[family_folder] = os.listdir(family_path)
                    for root, dirs, filenames in os.walk(family_path):
                        for filename in filenames:
                            file_path = os.path.join(root, filename)
                            used_space += os.path.getsize(file_path)
    else:
        # 查找用户对应的家人文件夹
        family_folder = None
        if os.path.exists(FILE_DIR):
            for folder in os.listdir(FILE_DIR):
                account_file = os.path.join(FILE_DIR, folder, 'FA.txt')
                if os.path.exists(account_file):
                    try:
                        with open(account_file, 'r', encoding='utf-8') as f:
                            username_in_file = f.readline().strip()
                            if username_in_file == session['username']:
                                family_folder = folder
                                break
                    except Exception as e:
                        logging.error(f'读取 {account_file} 时出错: {e}')
        if family_folder:
            folder_path = os.path.join(FILE_DIR, family_folder)
            if os.path.isdir(folder_path):
                files[family_folder] = os.listdir(folder_path)
                for root, dirs, filenames in os.walk(folder_path):
                    for filename in filenames:
                        file_path = os.path.join(root, filename)
                        used_space += os.path.getsize(file_path)
    
    try:
        stat = os.statvfs(FILE_DIR) if hasattr(os, 'statvfs') else None
        if stat:
            total_space = stat.f_frsize * stat.f_blocks
        else:
            import ctypes
            free_bytes = ctypes.c_ulonglong(0)
            ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(FILE_DIR), None, None, ctypes.pointer(free_bytes))
            total_space = free_bytes.value / (1 - stat.f_bavail / stat.f_blocks) if stat else free_bytes.value
    except Exception as e:
        logging.error(f'获取磁盘空间信息时出错: {e}')
        total_space = 0
    
    remaining_space = total_space - used_space
    return render_template('index.html', files=files, remaining_space=remaining_space, total_space=total_space, used_space=used_space)

# 下载文件路由
@app.route('/download/<family_name>/<file_name>')
def download(family_name, file_name):
    if 'username' not in session:
        return redirect(url_for('login'))
    file_path = os.path.join(FILE_DIR, family_name, file_name)
    try:
        # 启用缓存和断点续传优化下载速度
        return send_file(file_path, as_attachment=True, conditional=True, max_age=3600)
    except Exception as e:
        logging.error(f'下载文件 {file_path} 时出错: {e}')
        return '文件下载失败，请联系管理员。', 500

@app.route('/upload', methods=['POST'])
def upload():
    """
    处理文件上传请求，将文件保存到对应家人的文件夹中。

    :return: 重定向到主页
    """
    if 'username' not in session:
        return redirect(url_for('login'))
    family_name = request.form.get('family_name')
    files = request.files.getlist('file')
    if family_name and files:
        if session['username'] != 'admin':
            # 查找非管理员用户对应的家人文件夹
            user_family_folder = None
            if os.path.exists(FILE_DIR):
                for folder in os.listdir(FILE_DIR):
                    account_file = os.path.join(FILE_DIR, folder, 'FA.txt')
                    if os.path.exists(account_file):
                        try:
                            with open(account_file, 'r', encoding='utf-8') as f:
                                username_in_file = f.readline().strip()
                                if username_in_file == session['username']:
                                    user_family_folder = folder
                                    break
                        except Exception as e:
                            logging.error(f'读取 {account_file} 时出错: {e}')
            if user_family_folder != family_name:
                logging.warning(f'非管理员用户 {session["username"]} 尝试上传到非自己的文件夹 {family_name}')
                return '非管理员只能上传到自己的文件夹', 403
        try:
            for file in files:
                if file:
                    add_file(family_name, file)
                    logging.info(f'用户 {session.get("username")} 成功为 {family_name} 上传文件 {file.filename}')
        except Exception as e:
            logging.error(f'用户 {session.get("username")} 为 {family_name} 上传文件时出错: {e}')
    return redirect(url_for('index'))

@app.route('/delete/<family_name>/<file_name>', methods=['POST'])
def delete(family_name, file_name):
    """
    处理文件删除请求，根据用户权限删除文件。

    :param family_name: 家人文件夹名称
    :param file_name: 文件名称
    :return: 重定向到主页
    """
    if 'username' not in session:
        return redirect(url_for('login'))
    if delete_file(family_name, file_name):
        logging.info(f'用户 {session["username"]} 成功删除 {family_name} 文件夹中的 {file_name} 文件')
    else:
        logging.warning(f'用户 {session["username"]} 尝试删除不存在的文件: {family_name}/{file_name}')
    return redirect(url_for('index'))

# 新建文件夹路由
@app.route('/create_folder', methods=['POST'])
def create_folder_route():
    if 'username' not in session:
        return redirect(url_for('login'))
    family_name = request.form.get('family_name')
    folder_name = request.form.get('folder_name')
    if family_name and folder_name:
        if session['username'] != 'admin':
            # 查找非管理员用户对应的家人文件夹
            user_family_folder = None
            if os.path.exists(FILE_DIR):
                for folder in os.listdir(FILE_DIR):
                    account_file = os.path.join(FILE_DIR, folder, 'FA.txt')
                    if os.path.exists(account_file):
                        try:
                            with open(account_file, 'r', encoding='utf-8') as f:
                                username_in_file = f.readline().strip()
                                if username_in_file == session['username']:
                                    user_family_folder = folder
                                    break
                        except Exception as e:
                            logging.error(f'读取 {account_file} 时出错: {e}')
            if user_family_folder != family_name:
                logging.warning(f'非管理员用户 {session["username"]} 尝试在非自己的文件夹 {family_name} 中创建文件夹')
                return '非管理员只能在自己的文件夹中创建文件夹', 403
        if create_folder(family_name, folder_name):
            logging.info(f'用户 {session["username"]} 成功为 {family_name} 创建文件夹 {folder_name}')
        else:
            logging.warning(f'用户 {session["username"]} 尝试为 {family_name} 创建文件夹 {folder_name} 失败')
    return redirect(url_for('index'))



# 复制文件或文件夹路由
@app.route('/copy', methods=['POST'])
def copy_route():
    if 'username' not in session:
        return redirect(url_for('login'))
    src_family = request.form.get('src_family')
    src_name = request.form.get('src_name')
    dst_family = request.form.get('dst_family')
    dst_name = request.form.get('dst_name')
    if src_family and src_name and dst_family and dst_name:
        if session['username'] != 'admin':
            # 查找非管理员用户对应的家人文件夹
            user_family_folder = None
            if os.path.exists(FILE_DIR):
                for folder in os.listdir(FILE_DIR):
                    account_file = os.path.join(FILE_DIR, folder, 'FA.txt')
                    if os.path.exists(account_file):
                        try:
                            with open(account_file, 'r', encoding='utf-8') as f:
                                username_in_file = f.readline().strip()
                                if username_in_file == session['username']:
                                    user_family_folder = folder
                                    break
                        except Exception as e:
                            logging.error(f'读取 {account_file} 时出错: {e}')
            if user_family_folder not in [src_family, dst_family]:
                logging.warning(f'非管理员用户 {session["username"]} 尝试复制非自己文件夹中的文件或文件夹')
                return '非管理员只能复制自己文件夹中的文件或文件夹', 403
        src = os.path.join(FILE_DIR, src_family, src_name)
        dst = os.path.join(FILE_DIR, dst_family, dst_name)
        if copy_file_or_folder(src, dst):
            logging.info(f'用户 {session["username"]} 成功从 {src} 复制到 {dst}')
        else:
            logging.warning(f'用户 {session["username"]} 尝试从 {src} 复制到 {dst} 失败')
    return redirect(url_for('index'))

# 移动文件或文件夹路由
@app.route('/move', methods=['POST'])
def move_route():
    if 'username' not in session:
        return redirect(url_for('login'))
    src_family = request.form.get('src_family')
    src_name = request.form.get('src_name')
    dst_family = request.form.get('dst_family')
    dst_name = request.form.get('dst_name')
    if src_family and src_name and dst_family and dst_name:
        if session['username'] != 'admin':
            # 查找非管理员用户对应的家人文件夹
            user_family_folder = None
            if os.path.exists(FILE_DIR):
                for folder in os.listdir(FILE_DIR):
                    account_file = os.path.join(FILE_DIR, folder, 'FA.txt')
                    if os.path.exists(account_file):
                        try:
                            with open(account_file, 'r', encoding='utf-8') as f:
                                username_in_file = f.readline().strip()
                                if username_in_file == session['username']:
                                    user_family_folder = folder
                                    break
                        except Exception as e:
                            logging.error(f'读取 {account_file} 时出错: {e}')
            if user_family_folder not in [src_family, dst_family]:
                logging.warning(f'非管理员用户 {session["username"]} 尝试移动非自己文件夹中的文件或文件夹')
                return '非管理员只能移动自己文件夹中的文件或文件夹', 403
        src = os.path.join(FILE_DIR, src_family, src_name)
        dst = os.path.join(FILE_DIR, dst_family, dst_name)
        if move_file_or_folder(src, dst):
            logging.info(f'用户 {session["username"]} 成功从 {src} 移动到 {dst}')
        else:
            logging.warning(f'用户 {session["username"]} 尝试从 {src} 移动到 {dst} 失败')
    return redirect(url_for('index'))

def rename_file_or_folder(old_path, new_path):
    """
    重命名文件或文件夹。

    :param old_path: 旧文件或文件夹路径
    :param new_path: 新文件或文件夹路径
    :return: 重命名成功返回 True，失败返回 False
    """
    try:
        if os.path.exists(old_path):
            os.rename(old_path, new_path)
            logging.info(f'成功将 {old_path} 重命名为 {new_path}')
            return True
        else:
            logging.warning(f'尝试重命名不存在的文件或文件夹: {old_path}')
            return False
    except Exception as e:
        logging.error(f'重命名 {old_path} 到 {new_path} 时出错: {e}')
        return False

@app.route('/rename', methods=['POST'])
def rename():
    """
    处理文件或文件夹重命名请求，根据用户权限进行重命名操作。

    :return: 重定向到主页
    """
    if 'username' not in session:
        return redirect(url_for('login'))
    old_name = request.form.get('old_name')
    new_name = request.form.get('new_name')
    if old_name and new_name:
        old_path = os.path.join(FILE_DIR, old_name)
        new_path = os.path.join(FILE_DIR, new_name)
        if session['username'] != 'admin':
            # 查找非管理员用户对应的家人文件夹
            user_family_folder = None
            if os.path.exists(FILE_DIR):
                for folder in os.listdir(FILE_DIR):
                    account_file = os.path.join(FILE_DIR, folder, 'FA.txt')
                    if os.path.exists(account_file):
                        try:
                            with open(account_file, 'r', encoding='utf-8') as f:
                                username_in_file = f.readline().strip()
                                if username_in_file == session['username']:
                                    user_family_folder = folder
                                    break
                        except Exception as e:
                            logging.error(f'读取 {account_file} 时出错: {e}')
            if not old_path.startswith(os.path.join(FILE_DIR, user_family_folder)) or not new_path.startswith(os.path.join(FILE_DIR, user_family_folder)):
                logging.warning(f'非管理员用户 {session["username"]} 尝试重命名非自己文件夹中的文件或文件夹')
                return '非管理员只能重命名自己文件夹中的文件或文件夹', 403
        if rename_file_or_folder(old_path, new_path):
            logging.info(f'用户 {session["username"]} 成功将 {old_name} 重命名为 {new_name}')
        else:
            logging.warning(f'用户 {session["username"]} 尝试重命名 {old_name} 到 {new_name} 失败')
    return redirect(url_for('index'))



if __name__ == '__main__':
    # 添加示例用户
    add_user('admin', 'admin123')

    f = Figlet(font='slant')
    time.sleep(1)
    print(f.renderText('EasyLanNetdisk'))
    print('                             By Codefish')
    print("                                1.0.5")
    
    # 获取本机 IP 地址
    def get_local_ip():
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(('8.8.8.8', 80))
            local_ip = s.getsockname()[0]
            s.close()
            return local_ip
        except Exception as e:
            logging.error(f'获取本机 IP 地址时出错: {e}')
            return '未知 IP'
    
    local_ip = get_local_ip()
    print("")
    print(f'               Service startup in http://{local_ip}:5000')

    app.run(host='0.0.0.0', port=5000, debug=True, threaded=True)